home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 August: Tool Chest / Dev.CD Aug 98 TC.toast / Sample Code / Interapplication Communication / MenuScripter 4.0 / Associated Documentation / 7Edit Read Me - Part 1 next >
Encoding:
Text File  |  1996-07-09  |  30.8 KB  |  660 lines  |  [TEXT/ttxt]

  1. 7Edit 3.1
  2.  
  3. Original version by Jon Lansdell and Nigel Humphreys
  4.  
  5. 3.1 updates by Greg Sutton
  6.  
  7. Introduction
  8.  
  9. The aim of this document and it's associated application with code is to give an example of creating a Scriptable Application. A scriptable application goes far beyond supporting just the basic four events of the Required Suite (Open Application, Open Documents, Print Documents and Quit Application).
  10.  
  11. This latest version of 7Edit has been designed to be very similar to the Scriptable Text Editor. The example may not be as fully scriptable as the Scriptable Text Editor but it should give you good idea of how to make your application scriptable. In particular it shows you how to handle the 'whose' clause in AppleScript.
  12.  
  13. This version of 7Edit also demonstrates  QuickDraw GX printing and Drag Manager Support.
  14.  
  15. In this document I shall assume that you know or understand AppleScript as this will form a basis of why and how certain AppleEvents are supported. If you do not know anything of AppleScript then there are quite a few good books on the subject.
  16.  
  17. I also highly recommend that you do the Developer University Mini Course - Apple Events/AppleScript Programming Tutorial. This course goes into more general detail than this documentation. There are also some good articles in develop about the Object Model, designing your scripting language and supporting suites. See the references at the end of this document.
  18.  
  19. How to use 7Edit
  20.  
  21. This application is basically a simple text editing program. The major difference between this application and other text editing programs, such as Simple Text, is that 7Edit is scriptable. This means that the program can be controlled through the scripting language AppleScript. So, for example, the following script would return a list of every word in the front window of 7Edit which contained the letter k:
  22.  
  23. tell application "7Edit"
  24.    get every word of document 1 where it contains "k"
  25. end tell
  26.  
  27. There are some sample scripts in the ' Example Scripts' folder. Each of these scripts contains a number of scripts to try out, most of them are commented out (using '--'). To run a script, open the file with Script Editor and comment out all the lines except the line you want and run it.
  28.  
  29. Note: The name of the application may vary depending on the version of 7Edit you run e.g. the Metrowerks Power PC version is called "7Edit.MW.PPC". If you find you are not getting the results you suspect make sure another version of 7Edit hasn't been launched in the background.
  30.  
  31. Building the Application
  32.  
  33. MenuScripter compiles under :
  34.     Metrowerks CodeWarrior 7 68K and PPC
  35.     Symantec C++ 8.0.1
  36.     Symantec 7.0.4
  37.   MPW E.T.O. #18- 'Latest MPW': MPW C, PPCC, Symantec C++ for MPW and MrC.
  38.  
  39. - Symantec C++ for Macintosh build notes
  40.  
  41. The Symantec environments are using a slightly older version of the Universal Interfaces than MPW and CodeWarrior. You will need to use a later version of the Universal Interfaces than is provided with the Symantec products, or make some changes to the source code. To change the Universal Interfaces, simply place brackets around the existing folder and place the folder containing the later version into the same folder as the existing ones. The brackets will prevent the development environment from using the files contained within the old folder. The later version can be retrieved off the latest ETO CD, it's the 'CIncludes' folder in the Latest MPW: 
  42.  
  43. 'MPW Pro:Tools - Objects:MPW:Latest MPW:MPW:Interfaces:CIncludes'
  44.  
  45. You will also need to copy another couple of folders into this folder, these are 'GX Libraries' and 'GX Compatibility Interfaces':
  46.  
  47. 'MPW Pro:Essentials:U.S. System Software:System Extensions:QuickDraw GX:GX Libraries'
  48.  
  49. 'MPW Pro:Essentials:U.S. System Software:System Extensions:QuickDraw GX:GX Compatibility Interfaces'
  50.  
  51. Lastly you need to make sure that you have the 'QuickDrawGXLib.xcoff' library when compiling with Symantec C++ 8.0.1. This should be put in Symantec's 'PPC Libraries' folder and the library can be found from the following path:
  52.  
  53. 'MPW Pro:Tools - Objects:MPW:Latest MPW:MPW:Libraries:PPCLibraries:QuickDrawGXLib.xcoff'
  54.  
  55. It is recommended that you use precompiled headers with Symantec C++ 8.0.1. This will speed compilation and decrease the memory needed. Included in the project is 'Mac #includes.c' to precompile a new 'PPC Macheaders' select this file and  select Precompile or Precompile As… from the Build menu.
  56.  
  57. - MPW build notes
  58.  
  59. Two MPW make files are included: to build a 68K and a 'fat' version using 'Latest MPW' from ETO#18.  The make file for the 68K version of 7Edit is set up to use the default, which is MPW C. The 'fat' make file uses SC and MrC and is designed to compile with the interfaces and libraries from 'Latest MPW'. Instructions are provided on ETO for installing Symantec C++ for MPW 8.0.1 (do not use the version from 'PreRelease MPW' with the 'Latest MPW' interfaces and libraries) and using MrC with 'Latest MPW'. When using SC, you will need to alter the compiler options as appropriate and use the 'Latest MPW' interfaces and libraries rather than SCLibraries and SCIncludes. 
  60.  
  61. To compile either the 68K or 'fat' version of 7Edit under MPW you will need to copy the 'GX Libraries' and 'GX Compatibility Interfaces' from the QuickDraw GX release (see notes above for Symantec C++ for Macintosh) into your MPW CIncludes folder. You will also need to copy the version of 'DragLib' included with this project into your MPW:Libraries:Shared Libraries folder. Using older versions of DragLib will produce link errors, since a routine used by 7Edit is not included in earlier versions of DragLib.
  62.  
  63. How Does it Work?
  64.  
  65. I think the best way to show you how events are handled is to take you through a few example scripts explaining things as I go.
  66.  
  67. Select Example
  68.  
  69. The select event is a good event to begin with, the routines associated with it are used in many events and it can take varying types of parameters. So let's start with the following script:
  70.  
  71. tell application "7Edit"
  72.    select window 2
  73. end tell
  74.  
  75. The expected result of this script is to bring the window behind the front window i.e. window 2 to the front, or if there is only one window complain that the window does not exist. 7Edit does this, but what goes on in the background?
  76.  
  77. First of all we'll take a look at what the Apple Event Registry, well the Apple Event Registry Errata v3 at the moment, has to say about the select event.
  78.  
  79.         Event Class:                    kAEMiscStandards
  80.                         Event ID:                           kAESelect    = 'slct'
  81.  
  82.       Parameters
  83.  
  84.        keyDirectObject
  85.              Driescption:             Specifies the objects to be selected
  86.              Desc Type:                    typeObjectSpecifier
  87.              Required?                     Yes
  88.  
  89.        Reply
  90.  
  91.        keyAEErrorNumber
  92.        keyAEErrorString
  93.  
  94. This tells us that it is in the Miscellaneous Suite and that you can use kAESelect to refer to it within your program (as long as you include the appropriate header which in this case is 'AERegistry.h'). The first thing that needs to be done is to tell the Object Support Library that your application can handle this event. This is done by installing a handler, or a routine that gets called whenever your application gets one of these events. In 7Edit this is done in the InitAppleEvents() routine:
  95.  
  96. aevtErr = AEInstallEventHandler(kAEMiscStandards, kAESelect,
  97.                  NewAEEventHandlerProc(DoSelect), noRefCon, false);
  98.  
  99. The NewAEEventHandlerProc() around the reference to the routine is to allow your code to run on 68K and PPC machines.
  100.  
  101. So the DoSelect() routine, in 'SVAESelect.c',  now gets called when our script is run.
  102.  
  103. pascal OSErr    DoSelect(const AppleEvent *theAppleEvent,
  104.                                   AppleEvent *reply, long refcon)
  105. {
  106.    #pragma unused (refcon)
  107.  
  108.    AEDesc    directObj = {typeNull, NULL},
  109.                result = {typeNull, NULL};
  110.       OSErr        err;
  111.  
  112.    err = AEGetParamDesc(theAppleEvent, keyDirectObject,
  113.                                        typeWildCard, &directObj);
  114.     
  115.    // There was a direct parameter
  116.    if (directObj.descriptorType != typeNull)
  117.    err = SelectDesc(&directObj, &result);
  118.    else    // There was no direct parameter
  119.    {
  120.       err = PutPStringToDescriptor(&result,
  121.                   "\pYou have not specified an object to select");
  122.       if (noErr != err) goto done;
  123.       err = errAENoSuchObject;
  124.    }
  125.  
  126.    err = AddResultToReply(&result, reply, err);
  127.  
  128. done:    
  129.    if (directObj.dataHandle)
  130.      AEDisposeDesc(&directObj);
  131.    if (result.dataHandle)
  132.      AEDisposeDesc(&result);
  133.         
  134.    return(err);
  135. } // DoSelect
  136.  
  137. If we look back at the Apple Event Registry definition we see that we expect a keyDirectObject parameter. So the first thing we do is get that out of the apple event. If we get a keyDirectObject descriptor then SelectDesc() is called. It doesn't make sense to select nothing, so we create a text descriptor and this gets put in the reply so that a user can understand the error instead of getting something baffling (something that should be done more than it is in 7Edit). Lastly we tidy up by disposing of any descriptors we've created.
  138.  
  139. The next routine to take a look at is SelectDesc().
  140.  
  141. OSErr    SelectDesc(const AEDesc* aDesc, const AEDesc* result)
  142. {
  143.    AEDesc    selectDesc = {typeNull, NULL},
  144.    textDesc = {typeNull, NULL};
  145.    OSErr        err;
  146.     
  147.    if (typeObjectSpecifier == aDesc->descriptorType)
  148.       err = AEResolve(aDesc, kAEIDoMinimum, &selectDesc);
  149.    else
  150.       err = AEDuplicateDesc(aDesc, &selectDesc);
  151.         
  152.    if (noErr != err) goto done;
  153.     
  154.    switch (selectDesc.descriptorType)
  155.      {
  156.       case typeAEList:
  157.                   err = PutPStringToDescriptor(result,
  158.               "\pThis application cannot select a list of objects");
  159.                   if (noErr != err) goto done;
  160.                   err = errAETypeError;
  161.                break;
  162.  
  163.       case typeMyWndw:
  164.          err = SelectWindowDesc(&selectDesc);
  165.                break;
  166.             
  167.             default:
  168.                   err = AECoerceDesc(&selectDesc, typeMyText,&textDesc);
  169.                if (noErr != err) goto done;
  170.                   err = SelectTextDesc(&textDesc);
  171.    }
  172.     
  173. done:
  174.    if (selectDesc.dataHandle)
  175.       AEDisposeDesc(&selectDesc);
  176.    if (textDesc.dataHandle)
  177.       AEDisposeDesc(&textDesc);
  178.     
  179.     return(err);
  180. }
  181.  
  182. The first thing this routine does is check to see if the descriptor which we got as the direct parameter is an object specifier or not. In the case of an event sent due to our script, it will be. It will be an object specifier which specifies window two of our document. The Object Support Library can resolve an object specifier, with AEResolve(), into a descriptor that your application understands by using accessors that you've installed.
  183.  
  184. Let's digress and see how we would create an object specifier for a window. There is a routine that does do this for recording and reply purposes. Think of an object specifier as a description that AppleScript can take and convert into a readable form.
  185.  
  186. OSErr    MakeWindowObj(WindowPtr theWindow, AEDesc *result)
  187. {
  188.    AEDesc   nullDesc = {typeNull, NULL},
  189.             absoluteDesc = {typeNull, NULL};
  190.    long        windowIndex;
  191.       OSErr    err;
  192.     
  193.    windowIndex = GetNthWindowOfWindowPtr(theWindow);
  194.    if (! windowIndex)
  195.       return(errAENoSuchObject);
  196.     
  197.    err = AECreateDesc(typeInteger,(Ptr)&windowIndex,
  198.                           sizeof(windowIndex), &absoluteDesc);
  199.    if (noErr != err) goto done;
  200.     
  201.    err = CreateObjSpecifier(cWindow, &nullDesc,
  202.            formAbsolutePosition, &absoluteDesc, false, result);
  203.  
  204. done:
  205.    if (absoluteDesc.dataHandle)
  206.       AEDisposeDesc(&absoluteDesc);
  207.     
  208.    return(err);
  209. } // MakeWindowObj
  210.  
  211. We get the index of the window first - checking that it does actually exist. A descriptor for that index is then created and this is used in the creation of the object specifier. We use cWindow to say that this object is a window. The nullDesc is for the object that contains this window, a descriptor of typeNull describes the application itself in an object specifier. The formAbsolutePosition parameter says that the descriptor we supply to describe the object is an absolute position descriptor i.e. it is an index to the window. There are also other forms of descriptors, such as formName and formRange but we'll go into these later.
  212.  
  213. So an object specifier is a reference to an object in our application. When you see, in an AppleScript definition, that a handler requires a reference as a parameter. The parameter will be passed to that application as an object specifier. So back to what accessors do. Accessors are routines in your application that convert object specifiers into internal representations that your application can use. So the kind of accessor we want is one that takes an object specifier for a window and returns a WindowPtr, so that we can use the Toolbox routine BringToFront() to select our window.
  214.  
  215. We install an accessor routine that can do this in the InstallAccessors() routine.
  216.  
  217. err = AEInstallObjectAccessor(cWindow, typeNull,
  218.          NewOSLAccessorProc(WindowFromNullAccessor), 0, false);
  219.  
  220. Installing this accessor tells the Object Support Library that if it wants to resolve an object specifier for a window from the container class typeNull (i.e. the application) into a form our application understands, then call the WindowFromNullAccessor() routine.
  221.  
  222. So the WindowFromNullAccessor() routine will be called by the Object Support Library when we call AEResolve() in our SelectDesc() routine. How does WindowFromNullAccessor() get a WindowPtr for us?
  223.  
  224. pascal OSErr   WindowFromNullAccessor(DescType      wantClass,
  225.                                       const AEDesc  *container,
  226.                                       DescType      containerClass,
  227.                                       DescType      form, 
  228.                                       const AEDesc  *selectionData,
  229.                                       AEDesc        *value,
  230.                                       long          theRefCon)
  231. {
  232. #pragma unused (container,theRefCon)
  233.  
  234.    OSErr       err;
  235.     
  236.    // Can only handle cWindow and cDocument
  237.    if (wantClass != cWindow && wantClass != cDocument)
  238.       return(errAEWrongDataType);
  239.         
  240.       // Can only handle typeNull and typeMyAppl
  241.    if (containerClass != typeNull && containerClass != typeMyAppl)
  242.       return(errAENoSuchObject);
  243.     
  244.    switch (form)
  245.    {
  246.       case formAbsolutePosition:
  247.                   err = WindowFormAbsolutePosition(selectionData,value);
  248.                break;
  249.             
  250.       case formName:
  251.                   err = WindowFormName(selectionData, value);
  252.                break;
  253.             
  254.       default:
  255.          err = errAEBadTestKey;
  256.    }
  257.             
  258.    return(err);
  259. } // WindowFromNullAccessor
  260.  
  261. WindowFromNullAccessor() checks that it can handle the type of class wanted and also that it can do it from the given container type. The routine then calls a routine that can deal with the form of the data in the selectionData descriptor. 7Edit only handles two types of object specifier forms for a window. Earlier we saw how formAbsolutePosition was where the window was specified by an index. The formName, as you may expect ,is where the window (or object) is specified by it's name, this allows the following script to be resolved:
  262.  
  263. tell application "7Edit"
  264.       select window "Untitled"
  265. end tell
  266.  
  267. When we look at the WindowFormAbsolutePosition() routine we see that it's actually a bit more complicated than you'd expect. This is because the actual definition for formAbsolutePosition is that it specifies the position of an element via an index in relation to the beginning or end of it's container.
  268.  
  269. OSErr    WindowFormAbsolutePosition(const AEDesc*  selectionData,
  270.                                        AEDesc*  result)
  271. {
  272.    AEDesc    itemDesc = {typeNull, NULL};
  273.       short     windowCount,
  274.              index;
  275.       OSErr     err;
  276.  
  277.       windowCount = CountWindows();
  278.     
  279.    if (! windowCount)
  280.             return(errAEIllegalIndex);
  281.     
  282.       if (typeAbsoluteOrdinal == selectionData->descriptorType)
  283.       {
  284.       switch (*(DescType *)*selectionData->dataHandle)
  285.       {
  286.          case kAEFirst:
  287.             index = 1;
  288.                      break;
  289.     
  290.                   case kAELast:
  291.                         index = windowCount;
  292.                      break;
  293.     
  294.                   case kAEMiddle:
  295.                         index = (windowCount + 1) / 2;
  296.                      break;
  297.     
  298.                   case kAEAny:
  299.                         index = (Random() % windowCount) + 1;
  300.                      break;
  301.     
  302.                   case kAEAll:
  303.                         err = AECreateList(NULL, 0 , false, result);
  304.                         if (noErr != err) goto done;
  305.                 
  306.                         for (index = 1; index <= windowCount;index++)
  307.                         {
  308.                               err = GetDescOfNthWindow(index,&itemDesc);
  309.                               if (noErr != err) goto done;
  310.                     
  311.                               err = AEPutDesc(result, 0, &itemDesc);
  312.                               if (noErr != err) goto done;
  313.                     
  314.                               if (itemDesc.dataHandle)
  315.                                     AEDisposeDesc(&itemDesc);
  316.                         }
  317.                         // We have created our list descriptor
  318.             goto done; // so we can just
  319.          break;     // tidy up and return.
  320.                 
  321.                   default:
  322.                         err = errAETypeError;
  323.       }
  324.    }
  325.    else
  326.    err = GetIntegerFromDescriptor(selectionData, &index);
  327.  
  328.    if (noErr != err) goto done;
  329.     
  330.    if (index < 0)        // Handle negative indexes
  331.       index = windowCount + index + 1;
  332.         
  333.    if (index > windowCount || index <= 0)
  334.       err = errAEIllegalIndex;
  335.    else
  336.       err = GetDescOfNthWindow(index, result);
  337.  
  338. done:    
  339.    if (itemDesc.dataHandle)
  340.       AEDisposeDesc(&itemDesc);
  341.  
  342.    return(err);
  343. } // WindowFormAbsolutePosition
  344.  
  345. This means that all of the following scripts will also have their object specifier resolved through formAbsolutePosition:
  346.  
  347. tell application "7Edit" to select first window
  348.  
  349. tell application "7Edit" to select last window
  350.  
  351. tell application "7Edit" to select middle window
  352.  
  353. tell application "7Edit" to select some window
  354.  
  355. tell application "7Edit" to select every window
  356.  
  357. The odd script from this lot is the last one, as this can result in more than one window. The way we deal with this is to create a list that holds a description for each window. The last script will actually produce an error - this is because it doesn't make sense to select every window because only one window can ever be at the front. The following script makes perfect sense though:
  358.  
  359. tell application "7Edit" to close every window
  360.  
  361. If you look back at the SelectDesc() routine you'll see that if the descriptor is a list then it returns an error. However, if you look at the CloseDesc() routine you'll see that it actually recursively calls itself for every descriptor in the list given. Doing it in this way allows for lists within lists which you can get in more complicated scripts.
  362.  
  363. The WindowFormAbsolutePosition() routine also needs to be able to handle negative indexes. A negative index implies that the index should be taken backwards from the last window instead of forwards from the first window. This allows us to resolve the script below:
  364.  
  365. tell application "7Edit" to select window -1
  366.  
  367. We still haven't seen the WindowPtr that we need to be able to call BringToFront(). This is bundled up in the result descriptor of the GetDescOfNthWindow() routine.
  368.  
  369. OSErr    GetDescOfNthWindow(short index, AEDesc* result)
  370. {
  371.    WindowToken        theToken;
  372.    OSErr                 err = noErr;
  373.     
  374.    theToken.tokenWindow = GetWindowPtrOfNthWindow(index);
  375.     
  376.    if (theToken.tokenWindow)
  377.             err = AECreateDesc(typeMyWndw, (Ptr)&theToken,sizeof(theToken), result);
  378.       else
  379.             err = errAEIllegalIndex;
  380.  
  381.       return(err);
  382. }
  383.  
  384. Here we see that we stick the WindowPtr into a WindowToken. Tokens are the way you represent all the information you need to identify an object internally. They can be very complicated if you like, although none are in 7Edit. Here is how we define a WindowToken in 'SVToken.h':
  385.  
  386. struct WindowToken
  387. {
  388.     WindowPtr    tokenWindow;
  389. };
  390. typedef struct WindowToken WindowToken;
  391.  
  392. It is simple. This is because all our application needs to identify a window is it's WindowPtr. So this is our internal represenation for cWindow. 
  393.  
  394. Right, back to the SelectDesc() routine. We've now seen how the object specifier for the window was resolved into a descriptor of type typeMyWndw by calling AEResolve(). The routine then switches on this type and calls SelectWindowDesc():
  395.  
  396. OSErr    SelectWindowDesc(AEDesc* windowDesc)
  397. {
  398.    WindowToken        aWindowToken;
  399.       Size                  actualSize;
  400.       OSErr                 err;
  401.  
  402.    if (typeMyWndw != windowDesc->descriptorType)
  403.             return(errAETypeError);
  404.         
  405.       GetRawDataFromDescriptor(windowDesc, (Ptr)&aWindowToken, sizeof(aWindowToken), &actualSize);
  406.  
  407.    err = SelectWindowToken(&aWindowToken);
  408.     
  409.    return(err);
  410. }
  411.  
  412. This routine just checks that it is a window descriptor and if it is gets the WindowToken out of the descriptor. The token is then passed onto SelectWindowToken():
  413.  
  414. OSErr    SelectWindowToken(WindowToken* theToken)
  415. {
  416.    OSErr            err = noErr;
  417.     
  418.    if (! theToken->tokenWindow)
  419.          return(errAENoSuchObject);
  420.         
  421.       BringToFront(theToken->tokenWindow);
  422.                                     
  423.       return(err);
  424. }
  425.  
  426. This routine finally calls BringToFront() to select the window.
  427.  
  428. Get Data Example
  429.  
  430. The next example script to look at is a script that gets a property from an object. 
  431.  
  432. tell application "7Edit"
  433.     get length of last word of document 1
  434. end tell
  435.  
  436. Again of we have a look at what the Apple Event Registry says about Get Data, it goes something like the following.
  437.  
  438.         Event Class:                         kAECoreSuite
  439.         Event ID:                               kAEGetData
  440.  
  441.         Parameters
  442.  
  443.         keyDirectObject
  444.              Description:                                    The set of objects whose data is to be returned
  445.              Desc Type:                                        typeObjectSpecifier
  446.              Required or Optional?          Required
  447.  
  448.             keyAERequestedType
  449.           Description:                                   A descriptor list containing typeType descriptor     records. 
  450.           Descriptor Type:                         typeAEList
  451.          Required or Optional?           Optional (default value: the default descriptor     type of the
  452.                                                                  objects whose data is to be returned)
  453.  
  454.         Reply
  455.  
  456.         keyAEErrorNumber
  457.         keyAEErrorString
  458.  
  459. The main difference between Get Data and Select is that Get Data can have an optional parameter. Not that the optional parameter is always quite as the Apple Event Registry says. Anyway we won't use the optional parameter in this example. If you want to see a better example of optional parameters look at the DoNewElement() routine in 'SVAECreate.c'.
  460.  
  461. There is a handler installed in InitAppleEvents() that tells the Object Support Library (OSL) which routine to call when we get a get data event.
  462.  
  463. aevtErr = AEInstallEventHandler( kAECoreSuite, kAEGetData,
  464.               NewAEEventHandlerProc(DoGetData), noRefCon, false);
  465.  
  466. The routine DoGetData() retrieves the direct parameter and passes it onto HandleGetData(). HandleGetData() then resolves the direct parameter because it is an object specifier. However this object specifier is different from the specifier in the select example because it specifies a property of an element.
  467.  
  468. So how is the object specifier resolved? When an object specifier is resolved it effectively starts at the right of the specifier and works left. So the first part that the accessors need to resolve is 'document 1'. If we have a look at the accessors installed in InstallAccessors() we see that this is handled by the same routine as 'window 1' i.e. WindowFromNullAccessor().
  469.  
  470. err = AEInstallObjectAccessor(cDocument, typeNull,
  471.           NewOSLAccessorProc(WindowFromNullAccessor), 0, false);
  472.  
  473. We saw in the select example that this returned a descriptor of type typeMyWndw. So once this is resolved the Object Support Library tries to resolve 'every word of document 1' where 'document 1' has been resolved to typeMyWndw. Looking again at the accessors installed in InstallAccessors(), you can see that there is the following accessor:
  474.  
  475. err = AEInstallObjectAccessor(cWord, typeMyWndw,
  476.         NewOSLAccessorProc(TextElemFromTextAccessor), 0, false);
  477.  
  478. We saw earlier how a window was represented internally by a WindowToken, but what about the internal representation for a word, a character or a paragraph? Do we need a seperate type of token for each of these? As it turns out we can represent any kind of text internally with one type of token, our TextToken.
  479.  
  480. struct TextToken
  481. {
  482.     WindowPtr  tokenWindow;
  483.     short      tokenOffset;
  484.     short      tokenLength;
  485. };
  486. typedef struct TextToken TextToken;
  487.  
  488. We need a window, from which we can get the text edit handle, an offset into it's hText handle, and the length of the text. When it comes to turning a TextToken into an object specifier there is a routine GetTextTokenObjectSpecifier(), in 'SVAETextUtils.c', which tries it's best to make the most meaningful object specifier.
  489.  
  490. You may expect that we would have an accessor like TextElemFromWindowAccessor() that should be called at this stage after all 'document 1' resolved to typeMyWndw. This was true in the previous version of 7Edit. Now there is a coercion handler installed which can coerce a descriptor of typeMyWndw to a descriptor of type typeMyText. The resulting descriptor is a TextToken that includes all the text in the window.
  491.  
  492. pascal OSErr TextElemFromTextAccessor(DescType wantClass,
  493.                                       AEDesc   *container,
  494.                                       DescType containerClass,
  495.                                       DescType form,
  496.                                       AEDesc   *selectionData,
  497.                                       AEDesc   *value,
  498.                                       long     theRefCon)
  499. {
  500.    // Local variables declared
  501.     
  502.    // check for a list
  503.       if (typeAEList == container->descriptorType)
  504.       {
  505.       // List handling code
  506.    }
  507.    else
  508.    {        // We have a coercion handler from window to text
  509.       myErr = AECoerceDesc(container, typeMyText, &aDesc);
  510.       if (noErr != myErr) goto done;
  511.  
  512.       // Get the containing TextToken
  513.       GetRawDataFromDescriptor(&aDesc, (Ptr)&containerToken,
  514.                                 sizeof(containerToken), &actualSize);
  515.         
  516.       switch (form)
  517.       {
  518.          case formAbsolutePosition:
  519.                         myErr = TextFormAbsolutePosition(&containerToken,
  520.                                     selectionData, wantClass, value);
  521.          break;                                        
  522.          case formRange:
  523.                      myErr = TextFormRange(&containerToken, selectionData,
  524.                                                   wantClass, value);
  525.          break;
  526.                 
  527.                   case formRelativePosition:
  528.                         myErr = TextFormRelativePosition(&containerToken,
  529.                                    selectionData, wantClass, value);
  530.                      break;
  531.                 
  532.                   default:
  533.                         myErr = errAEBadKeyForm;
  534.       }
  535.    }
  536.  
  537. done:
  538.    // Clean up
  539.         
  540.       return(myErr);
  541. }    // TextElemFromTextAccessor
  542.  
  543. When TextElemFromTextAccessor() is called to resolve 'last word of document 1' the window descriptor gets coerced into a TextToken. 'Last word' is of formAbsolutePosition so the routine TextFormRelativePosition() is called where the words in the container descriptor, 'document 1', are counted and a descriptor to the last word is created. This descriptor is of typeMyText. 
  544.  
  545. Back to looking at the accessors installed in InstallAccessors(). This time we want a property from a TextToken descriptor of typeMyText. We can see that the accessor we want is:
  546.  
  547. err = AEInstallObjectAccessor(cProperty, typeMyText,
  548.        NewOSLAccessorProc(PropertyFromTextAccessor), 0, false);
  549.  
  550. Looking at the PropertyFromTextAccessor() routine we see that a we use a different token to internally represent a text property.
  551.  
  552. pascal OSErr PropertyFromTextAccessor(DescType      wantClass,
  553.                                       const AEDesc  *container,
  554.                                       DescType      containerClass,
  555.                                       DescType      form, 
  556.                                       const AEDesc  *selectionData,
  557.                                       AEDesc        *value,
  558.                                       long          theRefCon)
  559. {
  560.    // declare local variables
  561.     
  562.       // and check types
  563.         
  564.             // Try and coerce to a TextToken descriptor
  565.       err = AECoerceDesc(container, typeMyText, &textDesc);
  566.       if (noErr != err) goto done;
  567.     
  568.             // Get the TextToken
  569.       GetRawDataFromDescriptor(&textDesc, (Ptr)&aTextToken,
  570.                                  sizeof(aTextToken), &actualSize);
  571.             
  572.             // Make sure the selection data is typeType
  573.       err = AECoerceDesc(selectionData, typeType, &propertyDesc);
  574.       if (noErr != err) goto done;
  575.  
  576.          // Get the property
  577.       GetRawDataFromDescriptor(&propertyDesc, (Ptr)&aProperty,
  578.                                     sizeof(aProperty),  &actualSize);
  579.             
  580.             //    Combine the two into single token
  581.       aTextPropToken.tokenTextToken = aTextToken;
  582.       aTextPropToken.tokenProperty  = aProperty;
  583.     
  584.       err = AECreateDesc(typeMyTextProp, (Ptr)&aTextPropToken,
  585.                                    sizeof(aTextPropToken), value);
  586.  
  587. done:        
  588.       // Clean up
  589.         
  590.       return(err);
  591. } // PropertyFromTextAccessor
  592.  
  593. We need this token because a TextToken doesn't hold enough information. We need to know the text it refers to and the property of it. So we declare a TextPropToken as follows:
  594.  
  595. struct TextPropToken
  596. {
  597.     TextToken    tokenTextToken;
  598.     DescType     tokenProperty;
  599. };
  600. typedef struct TextPropToken TextPropToken;                
  601.  
  602. So PropertyFromTextAccessor() finally resolves the object specifier we were passed, 'last word of document 1' in to a TextPropToken descriptor of type typeMyTextProp. Now we can go back to HandleGetData().
  603.  
  604. OSErr    HandleGetData(AEDesc *theObj, AEDesc *dataDesc)
  605. {
  606.     // Declare local variables
  607.  
  608.     // Coerce if it's an object specifier
  609.     
  610.    switch (objTokenDesc.descriptorType)
  611.    {
  612.             case typeMyApplProp:
  613.                   err = GetApplicationProperty(&objTokenDesc, dataDesc);
  614.                break;
  615.             
  616.             case typeMyTextProp:
  617.                   err = GetTextProperty(&objTokenDesc, dataDesc);
  618.                break;
  619.             
  620.             case typeMyWindowProp:
  621.                   err = GetWindowProperty(&objTokenDesc, dataDesc);
  622.                break;
  623.             
  624.             case typeMyText:
  625.                   GetRawDataFromDescriptor(&objTokenDesc, (Ptr)&theTextToken,
  626.                                   sizeof(theTextToken), &tokenSize);
  627.             
  628.                   err = GetTextTextProperty(&theTextToken, dataDesc);
  629.                break;
  630.             
  631.             case typeAEList:
  632.                    // Handle a list
  633.             break;
  634.             
  635.             default:    
  636.                   err = errAEWrongDataType;
  637.     }
  638.  
  639. done:
  640.    // Clean up
  641.     
  642.       return(err);
  643. } // HandleGetData
  644.  
  645. Where we switch to GetTextProperty() to get the actual information rather than just an internal reference to that information. GetTextProperty() eventually switches on the property type in the TextPropToken and simply returns a descriptor for the length of the token which is the length of the word.
  646.  
  647. Now what would happen if we had the following script instead?
  648.  
  649. tell application "7Edit"
  650.     get length of every word of document 1
  651. end tell
  652.  
  653. On resolving up to 'every word of document 1' we would have a list of TextToken descriptors instead of just a single one. We then have to apply the length property to every item in the list. What we have done is install another accessor which is called when the Object Support Library wants to resolve a property from a list:
  654.  
  655. err = AEInstallObjectAccessor(cProperty, typeAEList,
  656.          NewOSLAccessorProc(PropertyFromListAccessor), 0, false);
  657.  
  658. This routine then steps through the list and converts any non property tokens into property tokens in much the same way as we converted a TextToken into a TextPropToken in PropertyFromTextAccessor(). The final result is then a list of property values. Try it out.
  659.  
  660.